package com.defangit.award.orm;

import com.defangit.award.domain.Award;
import com.defangit.award.domain.Prize;
import com.defangit.award.domain.WinningInfo;
import com.defangit.award.domain.WinningStatus;
import com.defangit.award.extend.point.WinningInfoListener;
import com.laogen.plugins.PluginManager;
import laogen.hibernate.CommonFinder;
import laogen.hibernate.CommonService;
import laogen.lang.exception.ParameterNotValidException;
import laogen.util.StringUtil;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Random;

/**
 * 用户中奖信息服务类
 */
public class WinningInfoService extends CommonService<WinningInfo> {
    private static Logger logger= LoggerFactory.getLogger(WinningInfoService.class);
    private static List<WinningInfoListener> listeners=PluginManager.getInstance()
            .getListeners(WinningInfoListener.class);

    public WinningInfo addWinning(WinningInfo info){
        if(info==null) throw ParameterNotValidException.nullParameter("中奖信息");
        add(info);
        commitSession();
        return info;
    }

    public boolean changeStatus(int winningId,WinningStatus status){
        String hql="update WinningInfo w set w.status=:status where w.id=:id";
        Session session=getSession();
        session.createQuery(hql)
            .setParameter("status",status)
            .setParameter("id",winningId)
            .executeUpdate();
        //@see PrizeUsedHandler
        commitSession();
        return true;
    }

    /**
     * 生成报名抽奖信息，即时开奖模型立即产生中奖记录，其他模型产生参与记录
     * @param awardId 要参加的抽奖活动id
     * @param openid 用户id
     * @return 中奖记录或参与记录
     */
    public  WinningInfo attendAward(int awardId,String openid){
        Award award=getSession().get(Award.class,awardId);
        synchronized (WinningInfoService.class){
            for(WinningInfoListener listener:listeners){
                if(!listener.beforeAttend(award,openid))
                    throw ParameterNotValidException.constraintNotSatisfy("用户抽奖条件","当前用户不满足抽奖条件");
            }
        }
        if(award==null)
            throw ParameterNotValidException.zeroIdOrEntityNonExist("奖项id");
        switch (award.getType()){
            case INSTANT:
                logger.info("即开即中抽奖模型：{}",award.getAwardName());
                return makePrizeInfo(award,openid);
            case UNIFIED_BY_TIME:
                logger.info("时间统一开奖模型：{}",award.getAwardName());
                return makeAttendInfo(award,openid);
            case UNIFIED_BY_ATTEND:
                logger.info("人数统一开奖模型：{}",award.getAwardName());
                return makeAttendInfo(award,openid);
            case UNIFIED_BY_MANNUAL:
                logger.info("人工统一开奖模型：{}",award.getAwardName());
                return makeAttendInfo(award,openid);
        }
        return null;
    }

    /**
     * 用户参与抽奖活动时产生中奖信息，适用于即开即中模型
     * @param award 抽奖活动信息
     * @param openid 用户id
     * @return 中奖记录
     */
    private WinningInfo makePrizeInfo(Award award,String openid){
        synchronized (WinningInfoService.class){
            int total=award.getPrizes().parallelStream().mapToInt(p->p.getPrizeAllowance()-p.getPrizeUsed()).sum();
            if(total==0){
                throw ParameterNotValidException.constraintNotSatisfy("奖项","当前抽奖奖项已全部发放，无法继续抽奖！");
            }
            Random rnd=new Random();
            int rst=rnd.nextInt(total);
            int rstTotal=0;
            for(Prize pz:award.getPrizes()){
                rstTotal+=pz.getPrizeAllowance()-pz.getPrizeUsed();
                if(rst<=rstTotal) {
                    WinningInfo info=new WinningInfo()
                            .setWinTime(LocalDateTime.now())
                            .setPrize(pz)
                            .setAwardId(award.getId())
                            .setStatus(WinningStatus.WINED)
                            .setOpenId(openid);
                    addWinning(info);
                    listeners.parallelStream().forEach(l->l.afterAnnounce(info));
                    return info;
                }
            }
            return null;
        }
    }

    /**
     * 用户参与抽奖活动时产生参与信息，适用于定时、定人数或者手工开奖模型
     * @param award 抽奖活动
     * @param openid 用户id
     * @return 参与信息
     */
    private WinningInfo makeAttendInfo(Award award,String openid){
        long cnt=findAll()
                .award(award.getId())
                .user(openid)
                .count();
        if(cnt>0)
            throw ParameterNotValidException.constraintNotSatisfy("抽奖报名","您已参加抽奖，请勿重复报名");
        return new WinningInfo()
                .setOpenId(openid)
                .setAwardId(award.getId());
    }

    public List<WinningInfo> announceAward(int awardId){
        return null;
    }

    /**
     * 为指定奖项产生所有中奖记录
     * @param prizeId 要开启的奖项id
     * @return 全部中奖记录
     */
    public List<WinningInfo> announcePrize(int prizeId){
        if(prizeId==0)
            throw ParameterNotValidException.zeroParameter("开奖奖项id");
        Prize prize=getSession().get(Prize.class,prizeId);
        if(prize==null)
            throw ParameterNotValidException.zeroIdOrEntityNonExist("奖项id");
        //todo rest计算
        int rest=prize.getPrizeAllowance()-prize.getPrizeUsed();
        if(rest<=0){
            throw ParameterNotValidException.constraintNotSatisfy("奖项","奖项已全部发放，无法抽奖");
        }
        EnumSet<WinningStatus> statuses=EnumSet.of(WinningStatus.ATTENDED);
        List<WinningInfo> infos=findAll()
                .award(prize.getAward().getId())
                .status(statuses)
                .matchedAll();
        if(infos.size()<=0)
            throw ParameterNotValidException.zeroField("参与抽奖人数");
        List<WinningInfo> rsts=null;
        if(infos.size()<=rest){
            rsts=infos;
        }else{
            Random rnd=new Random();
            rsts=new ArrayList<>();
            for(int i=rest-1;i>=0;i--){
                logger.error("当前i：{}",i);
                int rst=rnd.nextInt(infos.size());
                rsts.add(infos.get(rst));
                infos.remove(rst);
            }
        }
        rsts.parallelStream().forEach(info->{
            info.setPrize(prize).setWinTime(LocalDateTime.now()).setStatus(WinningStatus.WINED);
        });
        saveOrUpdate(rsts);
        commitSession();
        return rsts;
    }

    /**
     * 为指定奖项产生一个中奖记录
     * @param prizeId 要产生的奖项id
     * @return 中奖记录
     */
    public WinningInfo announcePrizeOnce(int prizeId){
        if(prizeId==0)
            throw ParameterNotValidException.zeroParameter("开奖奖项id");
        Prize prize=getSession().get(Prize.class,prizeId);
        if(prize==null)
            throw ParameterNotValidException.zeroIdOrEntityNonExist("奖项id");
        int rest=prize.getPrizeAllowance()-prize.getPrizeUsed();
        if(rest<=0){
            throw ParameterNotValidException.constraintNotSatisfy("奖项","奖项已全部发放，无法抽奖");
        }
        EnumSet<WinningStatus> statuses=EnumSet.of(WinningStatus.ATTENDED);
        List<WinningInfo> infos=findAll()
                .award(prize.getAward().getId())
                .status(statuses)
                .matchedAll();
        if(infos.isEmpty())
            throw ParameterNotValidException.zeroField("参与抽奖人数");
        Random rnd=new Random();
        int rst=rnd.nextInt(infos.size());
        WinningInfo info=infos.get(rst);
        info.setStatus(WinningStatus.WINED)
                .setWinTime(LocalDateTime.now())
                .setPrize(prize);
        update(info);
        commitSession();
        PluginManager.getInstance().getListeners(WinningInfoListener.class)
                .parallelStream().forEach(lt->lt.afterAnnounce(info));
        return info;
    }
    public WinningInfoFinder findAll(){
        return new WinningInfoFinder();
    }

    public WinningInfoFinder findCanPrize(int prize){
        AwardService.AwardFinder finder=new AwardService().findAll().prize(prize);
        return new WinningInfoFinder().award(finder);
    }
    public class WinningInfoFinder extends CommonFinder<WinningInfo>{
        DetachedCriteria criteria=DetachedCriteria.forClass(WinningInfo.class);
        @Override
        public Criteria getCriteria() {
            return criteria.getExecutableCriteria(getSession());
        }

        @Override
        public DetachedCriteria getDetachedCriteria() {
            return criteria;
        }

        public WinningInfoFinder award(Integer awardId){
            if(awardId!=null && awardId!=0){
                criteria=criteria
                        .add(Restrictions.eq("awardId",awardId));
            }
            return this;
        }
        public WinningInfoFinder user(String openId){
            if(StringUtil.notEmpty(openId))
                criteria=criteria.add(Restrictions.eq("openId",openId));
            return this;
        }

        public WinningInfoFinder status(EnumSet<WinningStatus> status){
            if(status!=null)
                criteria=criteria.add(Restrictions.in("status",status));
            return this;
        }

        public WinningInfoFinder prize(Integer prizeId){
            if(prizeId!=null && prizeId!=0)
                criteria=criteria.add(Restrictions.eq("prize.id",prizeId));
            return this;
        }

        public WinningInfoFinder award(AwardService.AwardFinder finder){
            if(finder!=null){
                criteria=criteria.add(Subqueries.propertyEq("awardId",
                        finder.getDetachedCriteria().setProjection(Projections.id()))
                );
            }
            return this;
        }
    }
}
